﻿using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
using Newtonsoft.Json;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.language;
using Umbraco.Forms.Data.Storage;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;

namespace Cultiv.RestContour
{
    public static class Helper
    {
        internal static string GetAllForms(string stringType)
        {
            using (var formStorage = new FormStorage())
            {

                if (string.IsNullOrEmpty(stringType) || stringType == "json")
                    return GetJsonForObject(formStorage.GetAllForms());

                var xmlString = HttpUtility.HtmlDecode(SerializeObject(formStorage.GetAllForms()));
                return xmlString;
            }
        }

        internal static string GetFormFields(string formGuid, string stringType)
        {
            using (var formStorage = new FormStorage())
            {
                var currentForm = formStorage.GetForm(new Guid(formGuid));

                if (string.IsNullOrEmpty(stringType) || stringType == "json")
                    return GetJsonForObject(currentForm);

                var xmlString = HttpUtility.HtmlDecode(SerializeObject(currentForm));
                return xmlString;
            }
        }

        internal static string GetPrevaluesForField(string formGuid, string fieldId, string stringType)
        {
            using (var formStorage = new FormStorage())
            {
                var currentForm = formStorage.GetForm(new Guid(formGuid));
                foreach (var field in currentForm.AllFields)
                {
                    var currentField = field;
                    if (currentField.Id.ToString() != fieldId) continue;

                    // Apparently this is necessary as sometimes the incorrect 
                    // prevalues are returned, see:
                    // http://our.umbraco.org/forum/umbraco-pro/contour/13937-API-not-returning-correct-prevalues-for-Field
                    field.PreValueSource.Type.LoadSettings(field.PreValueSource);

                    if (string.IsNullOrEmpty(stringType) || stringType == "json")
                        return GetJsonForObject(currentField.PreValueSource.Type.GetPreValues(field));

                    var prevalues = HttpUtility.HtmlDecode(SerializeObject(currentField.PreValueSource.Type.GetPreValues(field)));
                    return prevalues;
                }
            }

            return "";
        }

        internal static string GetPrevaluesForFieldName(string formGuid, string fieldName, string stringType)
        {
            using (var formStorage = new FormStorage())
            {
                var currentForm = formStorage.GetForm(new Guid(formGuid));
                foreach (var field in currentForm.AllFields)
                {
                    var currentField = field;
                    if (currentField.Caption != fieldName) continue;

                    // Apparently this is necessary as sometimes the incorrect 
                    // prevalues are returned, see:
                    // http://our.umbraco.org/forum/umbraco-pro/contour/13937-API-not-returning-correct-prevalues-for-Field
                    field.PreValueSource.Type.LoadSettings(field.PreValueSource);

                    if (string.IsNullOrEmpty(stringType) || stringType == "json")
                        return GetJsonForObject(currentField.PreValueSource.Type.GetPreValues(field));

                    var prevalues = HttpUtility.HtmlDecode(SerializeObject(currentField.PreValueSource.Type.GetPreValues(field)));
                    return prevalues;
                }
            }

            return "";
        }

        internal static string GetField(string formGuid, string fieldId, string stringType)
        {
            using (var formStorage = new FormStorage())
            {
                var currentForm = formStorage.GetForm(new Guid(formGuid));

                var fields = from field in currentForm.AllFields
                             where field.Id.ToString() == fieldId
                             select field;

                foreach (var field in fields)
                {
                    // Apparently this is necessary as sometimes the incorrect 
                    // prevalues are returned, see:
                    // http://our.umbraco.org/forum/umbraco-pro/contour/13937-API-not-returning-correct-prevalues-for-Field
                    if (string.IsNullOrEmpty(stringType) || stringType == "json")
                        return GetJsonForObject(field);

                    var prevalues = HttpUtility.HtmlDecode(SerializeObject(field));
                    return prevalues;
                }
            }

            return "";
        }

        internal static string GetFieldByFieldName(string formGuid, string fieldName, string stringType)
        {
            using (var formStorage = new FormStorage())
            {
                var currentForm = formStorage.GetForm(new Guid(formGuid));

                var fields = from field in currentForm.AllFields
                             where field.Caption == fieldName
                             select field;

                foreach (var field in fields)
                {
                    // Apparently this is necessary as sometimes the incorrect 
                    // prevalues are returned, see:
                    // http://our.umbraco.org/forum/umbraco-pro/contour/13937-API-not-returning-correct-prevalues-for-Field
                    if (string.IsNullOrEmpty(stringType) || stringType == "json")
                        return GetJsonForObject(field);

                    var prevalues = HttpUtility.HtmlDecode(SerializeObject(field));
                    return prevalues;
                }
            }

            return "";
        }

        internal static bool EncodeForJavascript()
        {
            return String.IsNullOrEmpty(HttpContext.Current.Request["returnJSstring"]) == false 
                    && HttpContext.Current.Request["returnJSstring"] == "1";
        }

        ///  Cultiv: Adapted from: http://aspcode.net/c-encode-a-string-for-json-javascript
        ///  FUNCTION Enquote Public Domain 2002 JSON.org
        ///  @author JSON.org
        ///  @version 0.1
        ///  Ported to C# by Are Bjolseth, teleplan.no
        internal static string Enquote(string s)
        {
            if (String.IsNullOrEmpty(s))
                return "\"\"";

            int i;
            var len = s.Length;
            var sb = new StringBuilder(len + 4);
            string t;

            sb.Append('"');
            for (i = 0; i < len; i += 1)
            {
                var c = s[i];
                switch (c)
                {
                    case '>':
                    case '"':
                    case '\\':
                        sb.Append('\\');
                        sb.Append(c);
                        break;
                    case '\b':
                        sb.Append("\\b");
                        break;
                    case '\t':
                        sb.Append("\\t");
                        break;
                    case '\n':
                        sb.Append("\\n");
                        break;
                    case '\f':
                        sb.Append("\\f");
                        break;
                    case '\r':
                        sb.Append("\\r");
                        break;
                    default:
                        if (c < ' ')
                        {
                            var tmp = new string(c, 1);
                            t = "000" + Int32.Parse(tmp, NumberStyles.HexNumber);
                            sb.Append("\\u" + t.Substring(t.Length - 4));
                        }
                        else
                        {
                            sb.Append(c);
                        }
                        break;
                }
            }
            sb.Append('"');
            return sb.ToString();
        }


        /// Cultiv: Adapted from: http://www.west-wind.com/weblog/posts/114530.aspx
        /// <summary>
        /// Encodes a string to be represented as a string literal. The format
        /// is essentially a JSON string.
        /// 
        /// The string returned includes outer quotes 
        /// Example Output: "Hello \"Rick\"!\r\nRock on"
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        internal static string EncodeJsString(string s)
        {
            var sb = new StringBuilder();
            sb.Append("\"");
            foreach (var c in s)
            {
                switch (c)
                {
                    case '\"':
                        sb.Append("\\\"");
                        break;
                    case '\\':
                        sb.Append("\\\\");
                        break;
                    case '\b':
                        sb.Append("\\b");
                        break;
                    case '\f':
                        sb.Append("\\f");
                        break;
                    case '\n':
                        sb.Append("\\n");
                        break;
                    case '\r':
                        sb.Append("\\r");
                        break;
                    case '\t':
                        sb.Append("\\t");
                        break;
                    default:
                        int i = c;
                        if (i < 32 || i > 127)
                        {
                            sb.AppendFormat("\\u{0:X04}", i);
                        }
                        else
                        {
                            sb.Append(c);
                        }
                        break;
                }
            }
            sb.Append("\"");

            return sb.ToString();
        }

        internal static string GetDictionaryItem(string key)
        {
            var culture = String.IsNullOrEmpty(HttpContext.Current.Request["culture"])
                              ? Thread.CurrentThread.CurrentUICulture.Name
                              : HttpContext.Current.Request["culture"];

            try
            {
                var language = Language.GetByCultureCode(culture);
                return new Dictionary.DictionaryItem(key).Value(language.id);
            }
            catch (Exception errDictionary)
            {
                HttpContext.Current.Trace.Warn("library", "Error returning dictionary item '" + key + "'", errDictionary);
                return String.Empty;
            }
        }

        internal static string GetJsonForObject(object @object)
        {
            var json = "";
            var serializer = new JsonSerializer
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Error
            };

            serializer.Error += SerializerErrorHandler;

            using (var ms = new MemoryStream())
            using (var writer = new JsonTextWriter(new StreamWriter(ms)))
            {
                serializer.Serialize(writer, @object);
                writer.Flush();
                json += Encoding.Default.GetString(ms.ToArray());
            }

            return json;
        }

        private static void SerializerErrorHandler(object sender, ErrorEventArgs e)
        {
            e.ErrorContext.Handled = true;
        }

        // The (de)serialize XML stuff is borrowed from:
        // http://geekswithblogs.net/paulwhitblog/archive/2007/07/20/114076.aspx

        /// <summary>
        /// To convert a Byte Array of Unicode values (UTF-8 encoded) to a complete String.
        /// </summary>
        /// <param name="characters">Unicode Byte Array to be converted to String</param>
        /// <returns>String converted from Unicode Byte Array</returns>
        private static String Utf8ByteArrayToString(Byte[] characters)
        {
            var encoding = new UTF8Encoding();
            var constructedString = encoding.GetString(characters);
            return (constructedString);
        }

        /// <summary>
        /// Method to convert a custom Object to XML string
        /// </summary>
        /// <param name="obj">Object that is to be serialized to XML</param>
        /// <returns>XML string</returns>
        private static string SerializeObject<T>(T obj)
        {
            try
            {
                var memoryStream = new MemoryStream();
                var xmlSerializer = new XmlSerializer(typeof(T));
                var xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
                xmlSerializer.Serialize(xmlTextWriter, obj);
                memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
                var xmlString = Utf8ByteArrayToString(memoryStream.ToArray());
                var xmlDocument = new XmlDocument();
                xmlDocument.LoadXml(xmlString.TrimWithUnicodeWhitespace());
                return xmlDocument.ToString();
            }
            catch (Exception e)
            {
                Log.Add(LogTypes.Error, -1, "Cultiv RestContour error serializing object to XML" + e.Message + " - " + e.StackTrace);
            }

            return "";
        }

        public static string TrimWithUnicodeWhitespace(this string stringToTrim)
        {
            return stringToTrim.Trim().Trim(new[] { '\uFEFF', '\u200B' });
        }
    }
}